package com.gitee.wsl.ext.coroutines


import com.gitee.wsl.concurrent.api.cancel.Cancellable
import com.gitee.wsl.concurrent.api.disposable.Disposable
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.cancellation.CancellationException
import kotlin.coroutines.startCoroutine
import kotlin.time.Duration.Companion.seconds


/**
 * 以 [parentContext] 作为基准、以当前 `receiver` 为主要结果合并两个 [CoroutineContext]，
 * （也就是使用当前 context 覆盖 [parentContext]）
 *
 * 例如：
 * ```
 * CoroutineName("name1").mergeWith(CoroutineName("name2"))
 * // 结果：CoroutineName("name1")
 * ```
 *
 *
 * 在这过程中同样“合并”二者的 [Job]:
 * - 如果都没有 [Job]，则创建一个 [SupervisorJob] 添加
 * - 如果 [parentContext] 中有 [Job]，则将其作为创建的 [SupervisorJob] 的 parent。
 * - 如果当前 `receiver` 有 [Job]，则将其作为创建的 [SupervisorJob] 的 parent。
 * - 如果二者都有 [Job]，则将当前 `receiver` 作为 parent，并通过 [linkTo] 链接到 [parentContext] 中的 [Job] 上。
 *
 * @see linkTo
 */
fun CoroutineContext.mergeWith(parentContext: CoroutineContext): CoroutineContext {
    val mergedContext = parentContext.minusKey(Job) + this.minusKey(Job)
    val currentJob = this[Job]
    val parentJob = parentContext[Job]

    return when {
        currentJob == null && parentJob == null -> mergedContext + SupervisorJob()
        currentJob == null -> mergedContext + SupervisorJob(parentJob)
        parentJob == null -> mergedContext + SupervisorJob(currentJob)
        else -> mergedContext + SupervisorJob(currentJob).apply { linkTo(parentJob) }
    }
}


fun CoroutineContext.onCancel(block: () -> Unit): Cancellable {
    var running = true
    launchUnScoped {
        try {
            while (running) delay(1.seconds)
        } catch (e: CancellationException) {
            if (running) block()
        }
    }
    return Cancellable { running = false }
}

fun CoroutineContext.launchUnScoped(block: suspend () -> Unit) {
    block.startCoroutine(object : Continuation<Unit> {
        override val context: CoroutineContext = this@launchUnScoped

        override fun resumeWith(result: Result<Unit>) {
            if (result.isFailure) {
                result.exceptionOrNull()?.printStackTrace()
            }
        }
    })
}

fun CoroutineContext.asDisposable(): Disposable =
    object : Disposable {
        override val isDisposed: Boolean get() = !isActive

        override fun dispose() {
            this@asDisposable.cancel()
        }
    }